from directions import *
from movementData import *
from point import Point

import logging, random

__all__ = ['Boat', 'MovementException']

class MovementException(Exception):
    pass

# From Seventh Sanctum Pirate Ship Namer:
# http://www.seventhsanctum.com/generate.php?Genname=pirateshipnamer
BOAT_NAMES = """Buccaneer's Foul Pearl
Davy Jones' Murderer
Davy Jones' Servant
Devil's Lightning
Devil's Thunder
Sea's Black Sword
Bloody Cruelty
Damnation of the Sea
Damned Dream
Dark Pride
Disgraceful Manta
Disgraceful Pride
Foul Hate
Foul Lightning
Greedy Whore
Hellish Gold
Hellish Slave
Horrible Hangman
Insanity of the West
Mad Wolf of Atlantis
Poison Trident
Pride of the Raider
Shameful Hate
Jewel of the Ocean
Serpent of the East
Vile Sword
Buccaneer's Dream
Devil's Cannon
Night's Lust
Ocean's Evil Plunder
Sea's Cry
Sea's Curse
Sea's Cutlass
Cursed Death
Death of Hell
Doom of Hell
Doom of the Caribbean
Doom of the Ocean
Evil Executioner
Fear of the Demon
Fearful Serpent
Hateful Serpent
Howling Serpent
Lust of the East
Lust of the Serpent
Mad Murderer
Poison Thunder of Hades
Scurvy Killer of Hell
Vile Grail""".splitlines()

class Boat(object):
    """
    Represents a boat in a player's fleet.
    """
    
    _fleet = None
    name = ""
    
    # Current turn
    actionsUsed = 0
    _plot = None

    # Maneuvering
    direction = North
    wind = None
    speedInfo = None
    ocean = None
    anchored = 0

    # Crewing
    crew = None
    maxCrewman = 99

    # Battling
    sinkHealth = 0          #When hull hits reach this number, start sinking.
    curCannon = 0         
    maxCannon = 0           #class
    initiative = 1          #2 for cutters
    hullHitsMax = 1         #10*class
    riggingHitsMax = 1      #5*class
    steeringHitsMax = 12    #constant

    log = logging.getLogger('pgame.engine.boat')

    def __init__(self, boatClass, ocean, owner, name=None, crew=(), pos=None):
        if pos is None:
            pos = Point(0, 0)
        if isinstance(boatClass, str):
            boatClass = (boatClass[:-1], boatClass[-1])
        self.numericClass = numericClass = int(boatClass[0])
        subClass = boatClass[1]
        self.maxCannon = numericClass
        if numericClass == 1:
            self.initiative +=1
        self.crew = [] + list(crew)
        self.hullHitsMax = 10*numericClass
        self.riggingHitsMax = 5*numericClass
        self.maxCrewman = 18*numericClass + 9 #NOTE: Very arbitrary.
        self.wind = None
        self.actionsUsed = 0
        self.direction = North
        self.ocean = ocean
        self.owner = owner
        owner.boats.append(self)
        self._plot = plot(self)
        if name is None:
            name = BOAT_NAMES[int(random.uniform(0,len(BOAT_NAMES)))]
        self.name = name
        ocean.append(self, pos)
        self.log = logging.getLogger('pgame.engine.boat')
        
        #Calculate speeds for speedInfo
        moveClass = numericClass
        if subClass == 'c':
            moveClass += 1
        elif subClass == 'w':
            moveClass -= 1
        self.speedInfo = {0: 9+moveClass, 1: 9+moveClass, 2: 7+moveClass, \
            3: 6+moveClass//2, 4: 0}
        
        self.curCannon = min(self.numericClass/2, 1)


    plot = property((lambda self: self._plot))
    

    def __str__(self):
        return self.name
    
    def _getDist(self, deltaD):
        if deltaD > 4:
            deltaD = 8-deltaD
        return self.wind.dist*self.speedInfo[int(deltaD)]
    
    def getAutoMove(self):
        """
        Does a drift for the current move, unless this boat has already moved,
        or unless this boat is anchored.
        """
        if not self.anchored:
            deltaD = self.direction.dir - self.wind.direction.dir
            # Just turn one point, but in the right direction.
            if deltaD == 0:
                adjustment = 1
            else:
                adjustment = deltaD/abs(deltaD)
            # Make sure it wraps around the compass.
            newDir = Direction((self.direction.dir - adjustment + 8) % 8)
            # Don't float too far.
            dist = self._getDist(newDir.dir)/3
            return move(newDir, dist)
        return move(self.direction, 0.0)
        
    def getPotentialMoves(self):
        """
        Returns the maximum distance this boat can move given the current plot.
        """
        currPercent = 0.0
        for amove in self.plot:
            deltaD = abs(self.wind.direction-amove.dir)
            movePercent = float(amove.dist) / self._getDist(deltaD)
            currPercent += movePercent
        percentLeft = 1.0 - currPercent
        moves = {n:0, ne:0, e:0, se:0, s:0, sw:0, w:0, nw:0}
        for direction in compass:
            #Calculate how far we can go
            deltaD = abs(self.wind.direction-direction)
            moves[direction] = int( percentLeft * self._getDist(deltaD) )
        return moves
    
    def validatePlot(self):
        """
        Checks the current plot to make sure it is valid. Since this function
        is typically called in the middle of a list assignment, it raises an
        exception if anything fails. The description gives the exact details.
        """
        currPercent = 0.0
        prevDir = None
        # Use some crew members to start the boat (??)
        actions = self.actionsUsed + self.numericClass
        for amove in self._plot:
            # The percent completed determines the move. For each move,
            # add the percent moved (vs. how far it could moved) to the
            # percent. If the percent doesn't considerably exceed 1.0,
            # it's fine.

            # Check for turning the boat
            if prevDir is not None and prevDir != amove.dir.dir:
                # Use some crew to turn the boat
                actions += self.numericClass
                if actions > self.actions:
                    raise MovementException, 'Not enough Actions'
            
            deltaAngle = self.wind.dir.angle - amove.dir.angle + math.pi / 16.0
            deltaD = int(abs(deltaAngle) * 4.0 / math.pi)
            maxDistInDir = self._getDist(deltaD)
            if maxDistInDir > 0:
                movePercent = float(amove.dist) / maxDistInDir
                currPercent += movePercent
            else:
                # Can't sail into the wind.  Period.
                currPercent = 1.07
                
            if currPercent > 1.0625:
                raise MovementException, 'The boat went too far'
            
            prevDir = amove.dir.dir
        return True
    
    def move(self):
        """
        Executes the plotted move
        """
        self.log.debug('Starting move phase for %s. Plot: %s' %
                            (self.name, self.plot))

        # Validate our plot
        newplot = self._plot
        assert newplot.owner is self, "Not our plot!"
        
        # Make sure we have an iterable collection of moves
        if not hasattr(newplot, '__iter__'):
            newplot = [ newplot ]
            
        if len(newplot) == 0:
            newplot = [ self.getAutoMove() ]
        
        # Execute the plot
        prevDir = None
        currPercent = 0.0
        for amove in newplot:
            """
            The percent completed determines the move. For each move, add the
            percent moved (vs. how far it could moved) to the percent. If the
            percent doesn't considerably exceed 1.0, it's fine.
            """
            # See if we need to turn the boat.
            if prevDir is not None and prevDir != amove.dir.dir:
                self.actionsUsed += self.numericClass
                if self.actionsUsed > self.actions:
                    raise MovementException, 'Not enough actions'
            
            # See how much of a maximum move we're taking
            deltaAngle = self.wind.dir.angle - amove.dir.angle + math.pi / 16.0
            deltaD = int(abs(deltaAngle) * 4.0 / math.pi)
            moveDist = self._getDist(deltaD)
            if moveDist:
                movePercent = float(amove.dist) / moveDist
                currPercent += movePercent
            else:
                # Can't sail into the wind, ever.
                currPercent = 1.1667
            if currPercent > 1.1667:
                raise MovementException, 'Went too far'

            # Execute the move
            self.ocean.moveItem(self, amove)
            prevDir = amove.dir.dir
            
        self.direction = amove.dir
        self._plot = plot(self, [])
        self.log.debug('Movement phase for %s finished.  Facing %s.' %
                            (self.name, self.direction))
        


    def getMaxMove(self, ndir):
        deltaAngle = self.wind.dir.angle - ndir.angle + math.pi / 16.0
        deltaD = int(abs(deltaAngle) * 4.0 / math.pi)
        if deltaD > 4:
            deltaD = 8 - deltaD
        return self.wind.dist * self.speedInfo[deltaD]
    
    def _getFleet(self):
        """
        Returns the current fleet.
        """
        return self._fleet
    
    def _getActions(self):
        """
        Returns the total actions available at the time called.
        """
        return len(self.crew)
    
    def _getActionsLeft(self):
        """
        Returns the actions available to use.
        """
        return len(self.crew) - self.actionsUsed
    
    def startTurn(self):
        """
        Resets boat data in prep for a new turn.
        """
        self.actionsUsed = 0
        self.wind = self.ocean.game.weather
        self._plot = plot(self)
        self.owner.onBoatStarted(self)

        if self.anchored:
            self.owner.onBoatFinished(self)
        
    def finishTurn(self):
        self.owner.onBoatFinished(self)
        
    actions = property(_getActions)
    actionsLeft = property(_getActionsLeft)
    fleet = property(_getFleet)
